package impl;

import hash.CompartimentoHash;
import hash.EncadeamentoExterior;
import templ.AbstractCore;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
import utils.Arquivos;

public class Core extends AbstractCore {

    private final int SIZE = 7;
    private EncadeamentoExterior instance;

    public Core() throws IOException {
        EncadeamentoExterior.criaHash(Arquivos.ARQ_TAB_HASH_EMPLOYEE, SIZE);
        EncadeamentoExterior.criaHash(Arquivos.ARQ_TAB_HASH_RELATIVE, SIZE);
        instance = new EncadeamentoExterior(SIZE);
    }

    @Override
    public boolean addEmployee(String name, int age, double salary) {
        boolean inserted = false;
        int totalEmployees = 0;
        int employeeId = 0;

        try {
            totalEmployees = getTotalEmployees();
            employeeId = getNextEmployeeID();
            if (instance.insertEmployee(employeeId, name, age, salary, totalEmployees) != -1) {
                inserted = true;
                setTotalEmployees(totalEmployees + 1);
                setThisEmployeeID(employeeId);
            }

        } catch (Exception e) {
            e.printStackTrace();
            inserted = false;
        }

        return inserted;
    }

    @Override
    public boolean removeEmployee(int employeeID) {
        boolean removed = false;
        int totalEmployees = 0;

        try {
            totalEmployees = getTotalEmployees();
            if (instance.deleteEmployee(employeeID) != -1) {
                removed = true;
                setTotalEmployees(totalEmployees - 1);
            }
        } catch (Exception e) {
            e.printStackTrace();
            removed = false;
        }

        return removed;
    }

    @Override
    public boolean editEmployee(int employeeID, String newName, int newAge, double newSalary) {
        boolean edited = false;
        try {
            if (instance.editEmployee(employeeID, newName, newAge, newSalary) != -1) {
                edited = true;
            } else {
                edited = false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            edited = false;
        }
        return edited;
    }

    @Override
    public boolean addRelative(String name, int age, String employeeName) {
        boolean inserted = false;
        int totalRelatives = 0;
        int relativeId = 0;
        int employeeID = 0;

        try {
            employeeID = searchEmployeeId(employeeName);
            if (employeeID != -1) {
                if (instance.searchEmployee(employeeID) != -1) {
                    totalRelatives = getTotalRelatives();
                    relativeId = getNextRelativeID();
                    if (instance.insertRelative(relativeId, name, age, employeeID, totalRelatives) != -1) {
                        inserted = true;
                        setTotalRelatives(totalRelatives + 1);
                        setThisRelativeID(relativeId);
                    }

                } else {
                    inserted = false;
                    JOptionPane.showMessageDialog(null, "Não existe Employee com o código fornecido!");
                }
            } else {
                JOptionPane.showMessageDialog(null, "Erro ao localizar código do funcionário!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            inserted = false;
        }

        return inserted;
    }

    @Override
    public boolean removeRelative(int relativeID) {
        boolean removed = false;
        int totalRelatives = 0;

        try {
            totalRelatives = getTotalRelatives();
            if (instance.deleteRelative(relativeID) != -1) {
                removed = true;
            }
            setTotalRelatives(totalRelatives - 1);
        } catch (Exception e) {
            e.printStackTrace();
            removed = false;
        }
        return removed;
    }

    @Override
    public boolean editRelative(int relativeID, String newName, int newAge, int newEmployeeID) {
        boolean edited = false;
        try {
            if (newEmployeeID == -1) {
                if (instance.editRelative(relativeID, newName, newAge, newEmployeeID) != -1) {
                    edited = true;
                } else {
                    edited = false;
                }
            } else {
                if (instance.searchEmployee(newEmployeeID) != -1) {
                    if (instance.editRelative(relativeID, newName, newAge, newEmployeeID) != -1) {
                        edited = true;
                    } else {
                        edited = false;
                    }
                } else {
                    edited = false;
                    JOptionPane.showMessageDialog(null, "Não existe Employee com o código fornecido!");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            edited = false;
        }
        return edited;
    }

    public int searchEmployeeId(String employeeName) throws Exception {
        List<Employee> outEmployees = searchEmployeeByName(null, employeeName);
        if (outEmployees.size() == 1) {
            return outEmployees.get(0).cod;
        } else {
            return -1;
        }
    }

    @Override
    public List<Employee> searchEmployeeByName(List<Employee> inEmployees, String name) throws Exception {
        if (inEmployees == null) {
            inEmployees = Arquivos.leEmployees(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);
        }
        List<Employee> outEmployees = new ArrayList<Employee>();
        for (Employee e : inEmployees) {
            String thisName = e.nome.toUpperCase().trim();
            String searchName = name.toUpperCase().trim();
            if (thisName.equals(searchName)) {
                outEmployees.add(e);
            }
        }
        return outEmployees;
    }

    @Override
    public List<Employee> searchEmployeeByAge(List<Employee> inEmployees, int minAge, int maxAge) throws Exception {
        if (inEmployees == null) {
            inEmployees = Arquivos.leEmployees(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);
        }
        List<Employee> outEmployees = new ArrayList<Employee>();
        if (Integer.valueOf(minAge) == -1) {
            minAge = Integer.MIN_VALUE;
        }
        if (Integer.valueOf(maxAge) == -1) {
            maxAge = Integer.MAX_VALUE;
        }

        for (Employee e : inEmployees) {
            if (e.age >= minAge && e.age <= maxAge) {
                outEmployees.add(e);
            }
        }
        return outEmployees;
    }

    @Override
    public List<Employee> searchEmployeeBySalary(List<Employee> inEmployees, double minSalary, double maxSalary) throws Exception {
        if (inEmployees == null) {
            inEmployees = Arquivos.leEmployees(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);
        }
        List<Employee> outEmployees = new ArrayList<Employee>();
        if (Double.valueOf(minSalary) == -1) {
            minSalary = Double.MIN_VALUE;
        }
        if (Double.valueOf(maxSalary) == -1) {
            maxSalary = Double.MAX_VALUE;
        }
        for (Employee e : inEmployees) {
            if (e.salary >= minSalary && e.salary <= maxSalary) {
                outEmployees.add(e);
            }
        }
        return outEmployees;
    }

    @Override
    public List<Employee> searchEmployeeByRelatives(List<Employee> inEmployees, int minRelatives, int maxRelatives) throws Exception {
        if (inEmployees == null) {
            inEmployees = Arquivos.leEmployees(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);
        }
        List<Employee> outEmployees = new ArrayList<Employee>();
        
        if (Integer.valueOf(minRelatives) == -1) {
            minRelatives = Integer.MIN_VALUE;
        }
        if (Integer.valueOf(maxRelatives) == -1) {
            maxRelatives = Integer.MAX_VALUE;
        }

        for (Employee e : inEmployees) {
            int numRelatives = ListRelativesByEmployee(e).size();
            if (numRelatives >= minRelatives && numRelatives <= maxRelatives) {
                outEmployees.add(e);
            }
        }
        
        return outEmployees;
    }
    
    public List<Relative> ListRelativesByEmployee(Employee e) throws Exception {
        List<Relative> inRelatives = Arquivos.leRelatives(Arquivos.ARQ_TAB_DADOS_RELATIVE);
        List<Relative> outRelatives = new ArrayList<Relative>();
        
        for(Relative r : inRelatives) {
            if(r.codEmployee == e.cod) {
                outRelatives.add(r);
            }
        }
        
        return outRelatives;
    }

    @Override
    public List<Relative> searchRelativeByName(List<Relative> inRelatives, String name) throws Exception {
        if (inRelatives == null) {
            inRelatives = Arquivos.leRelatives(Arquivos.ARQ_TAB_DADOS_RELATIVE);
        }
        List<Relative> outRelatives = new ArrayList<Relative>();
        for (Relative r : inRelatives) {
            String thisName = r.nome.toUpperCase().trim();
            String searchName = name.toUpperCase().trim();
            if (thisName.equals(searchName)) {
                outRelatives.add(r);
            }
        }
        return outRelatives;
    }

    @Override
    public List<Relative> searchRelativeByAge(List<Relative> inRelatives, int minAge, int maxAge) throws Exception {
        if (inRelatives == null) {
            inRelatives = Arquivos.leRelatives(Arquivos.ARQ_TAB_DADOS_RELATIVE);
        }
        List<Relative> outRelatives = new ArrayList<Relative>();
        if (Integer.valueOf(minAge) == -1) {
            minAge = Integer.MIN_VALUE;
        }
        if (Integer.valueOf(maxAge) == -1) {
            maxAge = Integer.MAX_VALUE;
        }
        for (Relative r : inRelatives) {
            if (r.age >= minAge && r.age <= maxAge) {
                outRelatives.add(r);
            }
        }
        return outRelatives;
    }

    /**
     * Método utilizado para salvar o último id de uma entidade salvo
     * @param path O caminho para o arquivo que persiste a informação
     * @param id O id que será salvo no arquivo
     * @throws IOException 
     */
    private void setThisID(String path, int id) throws IOException {
        try {
            RandomAccessFile raf = new RandomAccessFile(new File(path), "rw");
            raf.writeInt(id);
            raf.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * Método utilizado para salvar o último id de um Relative salvo
     * @param path O caminho para o arquivo que persiste a informação
     * @param id O id que será salvo no arquivo
     * @throws IOException 
     */
    private void setThisRelativeID(int relativeId) throws IOException {
        setThisID(Arquivos.REL_NEXT_ID_FILE, relativeId);
    }

    /**
     * Método utilizado para salvar o último id de um Employee salvo
     * @param path O caminho para o arquivo que persiste a informação
     * @param id O id que será salvo no arquivo
     * @throws IOException 
     */
    private void setThisEmployeeID(int employeeId) throws IOException {
        setThisID(Arquivos.EMP_NEXT_ID_FILE, employeeId);
    }

    /**
     * Método utilizado para descobrir o último id de entidade inserido
     * @param path O caminho para o arquivo que persiste a informação
     * @return O próximo id a ser utilizado
     * @throws IOException Caso haja um erro de entrada e saída
     */
    private int getNextID(String path) throws IOException {
        int lastID = 0;

        try {
            RandomAccessFile raf = new RandomAccessFile(new File(path), "r");
            lastID = raf.readInt();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return lastID + 1;
    }

    /**
     * Método utilizado para descobrir o último id de Relative inserido
     * @param path O caminho para o arquivo que persiste a informação
     * @return O próximo id a ser utilizado
     * @throws IOException Caso haja um erro de entrada e saída
     */
    private int getNextRelativeID() throws IOException {
        return getNextID(Arquivos.REL_NEXT_ID_FILE);
    }

    /**
     * Método utilizado para descobrir o último id de Emplyee inserido
     * @param path O caminho para o arquivo que persiste a informação
     * @return O próximo id a ser utilizado
     * @throws IOException Caso haja um erro de entrada e saída
     */
    private int getNextEmployeeID() throws IOException {
        return getNextID(Arquivos.EMP_NEXT_ID_FILE);
    }

    private int getTotalRegisters(String path) throws IOException {
        int total = 0;

        try {
            RandomAccessFile raf = new RandomAccessFile(new File(path), "r");
            raf.seek(4);
            total = raf.readInt();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return total;
    }

    private int getTotalRelatives() throws IOException {
        return getTotalRegisters(Arquivos.REL_NEXT_ID_FILE);
    }

    private int getTotalEmployees() throws IOException {
        return getTotalRegisters(Arquivos.EMP_NEXT_ID_FILE);
    }

    private void setTotalRegisters(String path, int total) throws IOException {
        try {
            RandomAccessFile raf = new RandomAccessFile(new File(path), "rw");
            raf.seek(4);
            raf.writeInt(total);
            raf.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void setTotalRelatives(int totalRelatives) throws IOException {
        setTotalRegisters(Arquivos.REL_NEXT_ID_FILE, totalRelatives);
    }

    private void setTotalEmployees(int totalEmployes) throws IOException {
        setTotalRegisters(Arquivos.EMP_NEXT_ID_FILE, totalEmployes);
    }

    public void print() {
        System.out.println("-------------- PRINT --------------");
        try {
            System.out.println("Employees: " + this.getTotalEmployees());
            System.out.println("Relatives: " + this.getTotalRelatives());
            System.out.println("Next Employee ID: " + this.getNextEmployeeID());
            System.out.println("Next Relative ID: " + this.getNextRelativeID());

            try {
                List<Employee> es = Arquivos.leEmployees(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);
                System.out.println("--- Registros de Employee Data ---");
                for (Employee e : es) {
                    System.out.println(e.toString());
                }
            } catch (Exception e) {
            }

            try {
                List<Relative> rs = Arquivos.leRelatives(Arquivos.ARQ_TAB_DADOS_RELATIVE);
                System.out.println("--- Registros de Relative Data ---");
                for (Relative r : rs) {
                    System.out.println(r.toString());
                }
            } catch (Exception e) {
            }

            try {
                List<CompartimentoHash> rs = Arquivos.leCompartimentos(Arquivos.ARQ_TAB_HASH_EMPLOYEE);
                System.out.println("--- Compartimentos de Employee Hash ---");
                int i = 0;
                for (CompartimentoHash r : rs) {
                    System.out.println("[" + i++ + "] - " + r);
                }
            } catch (Exception e) {
            }
            try {
                List<CompartimentoHash> rs = Arquivos.leCompartimentos(Arquivos.ARQ_TAB_HASH_RELATIVE);
                System.out.println("--- Compartimentos de Relative Hash ---");
                int i = 0;
                for (CompartimentoHash r : rs) {
                    System.out.println("[" + i++ + "] - " + r);
                }
            } catch (Exception e) {
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }
        System.out.println();
    }

    private void resetCounters() {
        try {
            this.setTotalEmployees(0);
            this.setTotalRelatives(0);
            this.setThisEmployeeID(0);
            this.setThisRelativeID(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void deleteFiles() {
        for (String s : Arquivos.fileSet(true)) {
            File f = new File(s);
            if (f.exists()) {
                f.delete();
            }
        }
    }

    public void reset() {
        try {
            System.out.println("-------------- RESET --------------");
            deleteFiles();
            resetCounters();
            EncadeamentoExterior.criaHash(Arquivos.ARQ_TAB_HASH_EMPLOYEE, SIZE);
            EncadeamentoExterior.criaHash(Arquivos.ARQ_TAB_HASH_RELATIVE, SIZE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println();
    }
}
